🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / androidApp / src / test / kotlin / org / meshtastic / app / ui / DeepLinkManifestConsistencyTest.kt
Displaying Raw • Download
androidApp/src/test/kotlin/org/meshtastic/app/ui/DeepLinkManifestConsistencyTest.kt 65a1f5ce4d0e4b8eafcd0bbfccafd41dc6ee888a (65a1f5ce) Text, 4.73 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.app.ui
Tff7b72import T7ee787org.meshtastic.core.common.util.CommonUri
Tff7b72import T7ee787org.meshtastic.core.navigation.DeepLinkRouter
Tff7b72import T7ee787org.w3c.dom.Element
Tff7b72import T7ee787java.io.File
Tff7b72import T7ee787javax.xml.parsers.DocumentBuilderFactory
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertNotNull
Tff7b72import T7ee787kotlin.test.assertTrue
Tff7b72import T7ee787kotlin.test.fail
T8b949e/**
* Guards against drift between [DeepLinkRouter] and the https App Links intent-filter (`android:autoVerify="true"`,
* host `meshtastic.org`) in `androidApp/src/main/AndroidManifest.xml`.
*
* Every top-level path segment routed by [DeepLinkRouter.route] must be declared as an `android:pathPrefix` in that
* filter — otherwise `https://meshtastic.org/{path}` links open in the browser instead of the app, even though the
* `meshtastic://` scheme works. The segments come straight from [DeepLinkRouter.topLevelPathSegments], the set
* [DeepLinkRouter.route] gates its dispatch on, so a new router segment fails here until the manifest declares it.
*/
Tff7b72class T56d364DeepLinkManifestConsistencyTest Tb4b4b4{
Tf0883e@Test
Tff7b72fun Td2a8ff`every canonical segment is actually routed by DeepLinkRouter`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3DeepLinkRouterTb4b4b4.Te6edf3topLevelPathSegmentsTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3segment Tff7b72-Tff7b72>
Te6edf3assertNotNullTb4b4b4(
Te6edf3DeepLinkRouterTb4b4b4.Te6edf3routeTb4b4b4(Te6edf3CommonUriTb4b4b4.Te6edf3parseTb4b4b4(Ta5d6ff"Ta5d6ffhttps://meshtastic.org/Tffd700$Te6edf3segmentTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffDeepLinkRouter.topLevelPathSegments lists /Tffd700$Te6edf3segmentTa5d6ff but route() has no branch for it — Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffadd the branch or remove the segment from the set and the manifestTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`app links intent filter declares a pathPrefix for every routed segment`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3prefixes Tff7b72= Te6edf3appLinkPathPrefixesTb4b4b4(Tb4b4b4)
Te6edf3DeepLinkRouterTb4b4b4.Te6edf3topLevelPathSegmentsTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3segment Tff7b72-Tff7b72>
Te6edf3assertTrueTb4b4b4(
Ta5d6ff"Ta5d6ff/Tffd700$Te6edf3segmentTa5d6ff" Tff7b72in Te6edf3prefixesTb4b4b4,
Ta5d6ff"Ta5d6ffAndroidManifest.xml autoVerify filter is missing <data android:pathPrefix=Ta5d6ff\"Ta5d6ff/Tffd700$Te6edf3segmentTa5d6ff\"Ta5d6ff /> — Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffhttps://meshtastic.org/Tffd700$Te6edf3segmentTa5d6ff will open in the browser instead of the appTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e/** Collects the pathPrefix values of the autoVerify (App Links) intent-filter for meshtastic.org. */
Tff7b72private Tff7b72fun Td2a8ffappLinkPathPrefixesTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3SetTff7b72<Tffa657StringTff7b72> Tb4b4b4{
Tff7b72val Te6edf3manifest Tff7b72= Te6edf3manifestFileTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3factory Tff7b72=
Te6edf3DocumentBuilderFactoryTb4b4b4.Te6edf3newInstanceTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3apply Tb4b4b4{
T8b949e// Harden against XXE even though we only parse our own manifest.
Te6edf3setFeatureTb4b4b4(Ta5d6ff"Ta5d6ffhttp://apache.org/xml/features/disallow-doctype-declTa5d6ff"Tb4b4b4, Tff7b72trueTb4b4b4)
Te6edf3setFeatureTb4b4b4(Ta5d6ff"Ta5d6ffhttp://xml.org/sax/features/external-general-entitiesTa5d6ff"Tb4b4b4, Tff7b72falseTb4b4b4)
Te6edf3setFeatureTb4b4b4(Ta5d6ff"Ta5d6ffhttp://xml.org/sax/features/external-parameter-entitiesTa5d6ff"Tb4b4b4, Tff7b72falseTb4b4b4)
Te6edf3isXIncludeAware Tff7b72= Tff7b72false
Te6edf3isExpandEntityReferences Tff7b72= Tff7b72false
Tb4b4b4}
Tff7b72val Te6edf3document Tff7b72= Te6edf3factoryTb4b4b4.Te6edf3newDocumentBuilderTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3parseTb4b4b4(Te6edf3manifestTb4b4b4)
Tff7b72val Te6edf3filters Tff7b72= Te6edf3documentTb4b4b4.Te6edf3getElementsByTagNameTb4b4b4(Ta5d6ff"Ta5d6ffintent-filterTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3prefixes Tff7b72= Te6edf3mutableSetOfTff7b72<Tffa657StringTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72for Tb4b4b4(Te6edf3i Tff7b72in T79c0ff0 Te6edf3until Te6edf3filtersTb4b4b4.Te6edf3lengthTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3filter Tff7b72= Te6edf3filtersTb4b4b4.Te6edf3itemTb4b4b4(Te6edf3iTb4b4b4) Tff7b72as Te6edf3Element
Tff7b72if Tb4b4b4(Te6edf3filterTb4b4b4.Te6edf3getAttributeTb4b4b4(Ta5d6ff"Ta5d6ffandroid:autoVerifyTa5d6ff"Tb4b4b4) Tff7b72!Tff7b72= Ta5d6ff"Ta5d6fftrueTa5d6ff"Tb4b4b4) Tff7b72continue
Tff7b72val Te6edf3dataElements Tff7b72= Te6edf3filterTb4b4b4.Te6edf3getElementsByTagNameTb4b4b4(Ta5d6ff"Ta5d6ffdataTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3attrs Tff7b72= Tb4b4b4(T79c0ff0 Te6edf3until Te6edf3dataElementsTb4b4b4.Te6edf3lengthTb4b4b4)Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3dataElementsTb4b4b4.Te6edf3itemTb4b4b4(Tffa657itTb4b4b4) Tff7b72as Te6edf3Element Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3attrsTb4b4b4.Te6edf3none Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3getAttributeTb4b4b4(Ta5d6ff"Ta5d6ffandroid:hostTa5d6ff"Tb4b4b4) Tff7b72=Tff7b72= Ta5d6ff"Ta5d6ffmeshtastic.orgTa5d6ff" Tb4b4b4}Tb4b4b4) Tff7b72continue
Tff7b72if Tb4b4b4(Te6edf3attrsTb4b4b4.Te6edf3none Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3getAttributeTb4b4b4(Ta5d6ff"Ta5d6ffandroid:schemeTa5d6ff"Tb4b4b4) Tff7b72=Tff7b72= Ta5d6ff"Ta5d6ffhttpsTa5d6ff" Tb4b4b4}Tb4b4b4) Tff7b72continue
Te6edf3attrsTb4b4b4.Te6edf3mapNotNullToTb4b4b4(Te6edf3prefixesTb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3getAttributeTb4b4b4(Ta5d6ff"Ta5d6ffandroid:pathPrefixTa5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3ifEmpty Tb4b4b4{ Tff7b72null Tb4b4b4} Tb4b4b4}
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3prefixesTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Te6edf3failTb4b4b4(Ta5d6ff"Ta5d6ffNo https App Links intent-filter for meshtastic.org found in Tffd700${Te6edf3manifestTb4b4b4.Te6edf3pathTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72return Te6edf3prefixes
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffmanifestFileTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3File Tff7b72= Te6edf3listOfTb4b4b4(Ta5d6ff"Ta5d6ffsrc/main/AndroidManifest.xmlTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffandroidApp/src/main/AndroidManifest.xmlTa5d6ff"Tb4b4b4)
Tb4b4b4.Te6edf3mapTb4b4b4(Tff7b72::Te6edf3FileTb4b4b4)
Tb4b4b4.Te6edf3firstOrNullTb4b4b4(Te6edf3FileTff7b72::Te6edf3existsTb4b4b4)
Tff7b72?: Te6edf3failTb4b4b4(Ta5d6ff"Ta5d6ffCould not locate AndroidManifest.xml from working directory Tffd700${Te6edf3FileTb4b4b4(Ta5d6ff"Tb4b4b4.Ta5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3absolutePathTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.06s